#include "precomp.h"

#include "EntityProjectile.h"
#include "../Entity.h"
#include "../../Map/Grid.h"

namespace Tmpl {

#define PROJECTILE_ENT_ARRAY_SIZE 256
#define PROJECTILE_JOBS 8
static DataValuePool<EntityProjectile> gProjectiles;
static Entity** gEntArrays[PROJECTILE_JOBS];

// EntitUnityProjectileystem setup functions
void EntityProjectileSystem::Init()
{
	for (int i=0; i<PROJECTILE_JOBS; ++i)
	{
		gEntArrays[i] = MALLOC64(PROJECTILE_ENT_ARRAY_SIZE, Entity*);
	}
	gProjectiles.Init();
}
void EntityProjectileSystem::Clear()
{
	for (int i=0; i<PROJECTILE_JOBS; ++i)
	{
		FREE64(gEntArrays[i]);
	}
	gProjectiles.Clear();
}
int EntityProjectileSystem::NewProjectile(Entity* owner, Vec2f pos, Vec2f facing, const UnitData& data)
{
	int id = gProjectiles.NewValue();

	EntityProjectile* value = gProjectiles.GetValue(id);
	value->mOwner = owner;
	value->mPos = pos;
	value->mFacing = facing;
	value->mSpeed = data.projectile.speed;
	value->mDamage = data.projectile.dmg;
	value->mAreaOfEffect = data.projectile.areaOfEffect;
	value->mFlightTime = data.projectile.flightTime;

	return id;
}
void EntityProjectileSystem::RemoveProjectile(int id)
{
	gProjectiles.RemoveValue(id);
}
EntityProjectile* EntityProjectileSystem::GetProjectile(int id)
{
	return gProjectiles.GetValue(id);
}
// EntityProjectile
// Updating
void EntityProjectileSystem::UpdateProjectileChunk(int chunk)
{
	int count = gProjectiles.GetValueCount();
	int step = count / PROJECTILE_JOBS;
	int from = step * chunk;
	int to = ((chunk==PROJECTILE_JOBS-1) ? count : step * (chunk+1));
	for (int i=from; i<to; ++i)
	{
		EntityProjectile* v = gProjectiles.GetUnmappedValue(i);
		bool explode = false;

		// update projectile
		Vec2f lastPos = v->mPos;
		v->mPos += v->mFacing * v->mSpeed;

		// decrease flight time
		if (v->mFlightTime >= 0.0f)
		{
			v->mFlightTime -= WORLD_STEP_TIME;
			explode = (v->mFlightTime <= 0.0f);
		}
		
		// solve collision
		Ray2f ray = Ray2f(lastPos, v->mPos);
		int entId = Grid::RayIntersect(ray, v->mPos);
		if (entId >= 0) 
		{
			explode = true;
			if (v->mAreaOfEffect <= 0.0f)
			{
				MessageSystem::AddMsgDamageDone(v->mOwner->GetOwnerId(), entId, v->mDamage, v->mOwner->GetId()); 
			}
		}	
		
		// are we finished?
		if (explode) 
		{
			// create area of effect explosion
			if (v->mAreaOfEffect > 0.0f)
			{
				int entCount = Grid::SelectEntitiesInRange(gEntArrays[chunk], PROJECTILE_ENT_ARRAY_SIZE, v->mPos, v->mAreaOfEffect, Entity::ENTITY_ALL);
				for (int e=0; e<entCount; ++e)
				{
					Entity* ent = gEntArrays[chunk][e];
					MessageSystem::AddMsgDamageDone(v->mOwner->GetOwnerId(), ent->GetId(), v->mDamage, v->mOwner->GetId());  
				}
			}

			// kill this projectile
			v->mOwner->Kill();
		}
	}
}
JobHandle EntityProjectileSystem::Update(JobHandle dependency)
{
	JobHandle ProjectileUpdate = JobManager::NewJob();
	for (int i=0; i<PROJECTILE_JOBS; ++i)
	{
		JobHandle job = JobManager::NewJob(&EntityProjectileSystem::UpdateProjectileChunk,i);
		JobManager::AddJobDependency(job, dependency);
		JobManager::AddExecuteJob(job);
		JobManager::AddJobDependency(ProjectileUpdate, job);
	}
	JobManager::AddExecuteJob(ProjectileUpdate);
	return ProjectileUpdate;
}
}